package org.ws.httphelper.support.annotation;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ws.httphelper.builder.ClientConfigBuilder;
import org.ws.httphelper.builder.RequestConfigBuilder;
import org.ws.httphelper.builder.annotation.ChildrenField;
import org.ws.httphelper.builder.annotation.Delete;
import org.ws.httphelper.builder.annotation.Get;
import org.ws.httphelper.builder.annotation.Header;
import org.ws.httphelper.builder.annotation.HttpClient;
import org.ws.httphelper.builder.annotation.HttpRequest;
import org.ws.httphelper.builder.annotation.Post;
import org.ws.httphelper.builder.annotation.Put;
import org.ws.httphelper.builder.annotation.RequestParam;
import org.ws.httphelper.builder.annotation.ResponseEntity;
import org.ws.httphelper.builder.annotation.ResponseField;
import org.ws.httphelper.common.PropertyUtil;
import org.ws.httphelper.core.pipeline.HttpHandler;
import org.ws.httphelper.model.config.ClientConfig;
import org.ws.httphelper.model.config.RequestConfig;
import org.ws.httphelper.model.field.ParamFieldType;
import org.ws.httphelper.model.field.ParamField;
import org.ws.httphelper.model.field.ParseField;
import org.ws.httphelper.model.field.ParseFieldType;
import org.ws.httphelper.model.http.ContentType;
import org.ws.httphelper.model.http.HttpMethod;
import org.ws.httphelper.model.http.ResponseType;
import org.ws.httphelper.template.DefaultHandlers;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;

public class AnnotationUtils {

    private static Logger log = LoggerFactory.getLogger(AnnotationUtils.class.getName());

    private AnnotationUtils(){}

    public static ClientConfig makeClientConfig(HttpClient httpClientAnn){
        if(httpClientAnn != null){
            return ClientConfigBuilder.builder()
                    .clientType(httpClientAnn.clientType())
                    .threadNumber(httpClientAnn.threadNumber())
                    .timeout(httpClientAnn.timeout())
                    .enableCookie(httpClientAnn.enableCookie())
                    .enableSsl(httpClientAnn.enableSsl())
                    .followRedirects(httpClientAnn.followRedirects())
                    .privateClient(httpClientAnn.privateClient())
                    .build();
        }
        else {
            return null;
        }
    }

    public static Map<String, Object> getRequestConfigMap(Annotation annotation) throws Exception {
        Map<String, Object> httpRequestMap;
        if(annotation instanceof HttpRequest){
            httpRequestMap = convertToMap((HttpRequest)annotation);
        }
        else {
            httpRequestMap = convertToMap(annotation);
        }
        return httpRequestMap;
    }

    public static Class getOutputClass(Map<String, Object> httpRequestMap)throws Exception {
        return  (Class) MapUtils.getObject(httpRequestMap,"responseEntity");
    }

    private static Map<String, Object> convertToMap(Annotation annotation)throws Exception{
        Map<String, Object> data = Maps.newHashMap();
        if(annotation instanceof Get
                || annotation instanceof Post
                || annotation instanceof Put
                || annotation instanceof Delete
        ){
            String simpleName = annotation.annotationType().getSimpleName();
            HttpMethod httpMethod = HttpMethod.valueOf(simpleName.toUpperCase());
            data.put("method",httpMethod);
            Method[] methods = annotation.annotationType().getDeclaredMethods();
            for (Method method : methods) {
                method.setAccessible(true);
                String name = method.getName();
                Object o = method.invoke(annotation);
                if(StringUtils.equals("value",name)){
                    data.put("rootUrl",o);
                }
                else {
                    data.put(method.getName(),o);
                }
            }
        }
        return data;
    }

    private static Map<String, Object> convertToMap(HttpRequest httpRequestAnn)throws Exception{
        Map<String, Object> data = Maps.newHashMap();
        Method[] methods = httpRequestAnn.annotationType().getDeclaredMethods();
        for (Method method : methods) {
            method.setAccessible(true);
            Object o = method.invoke(httpRequestAnn);
            data.put(method.getName(),o);
        }
        return data;
    }

    public static RequestConfig makeRequestConfig(Map<String, Object> httpRequestMap)throws Exception{
        if(MapUtils.isEmpty(httpRequestMap)){
            return null;
        }
        RequestConfigBuilder builder = RequestConfigBuilder.builder();
        HttpMethod httpMethod = (HttpMethod)MapUtils.getObject(httpRequestMap,"method");
        ResponseType responseType = (ResponseType)MapUtils.getObject(httpRequestMap,"responseType");
        // 添加默认handler
        builder.addHandler(DefaultHandlers.allHandlers());

        builder.rootUrl(MapUtils.getString(httpRequestMap,"rootUrl"))
                .method(httpMethod)
                .contentType((ContentType)MapUtils.getObject(httpRequestMap,"contentType"))
                .responseType(responseType)
                .charset(MapUtils.getString(httpRequestMap,"charset"));
        //
        Class<? extends HttpHandler>[] handlers = (Class<? extends HttpHandler>[])MapUtils.getObject(httpRequestMap,"handlers");
        if(handlers != null && handlers.length > 0){
            for (Class<? extends HttpHandler> handler : handlers) {
                builder.addHandler(handler.newInstance());
            }
        }
        // build Header
        Header[] headers = (Header[])MapUtils.getObject(httpRequestMap,"headers");
        if(headers != null && headers.length > 0){
            for (Header header : headers) {
                builder.addDefaultHeader(header.name(),header.value());
            }
        }
        // build请求参数自动
        Class requestEntity = (Class) MapUtils.getObject(httpRequestMap,"requestEntity");
        RequestParam[] requestParams = (RequestParam[])MapUtils.getObject(httpRequestMap,"requestParams");
        buildParamField(builder,requestEntity,requestParams);

        // build响应解析字段
        Class responseEntity = (Class) MapUtils.getObject(httpRequestMap,"responseEntity");
        ResponseField[] responseFields = (ResponseField[])MapUtils.getObject(httpRequestMap,"responseFields");
        buildParseField(builder,responseEntity,responseFields);
        return builder.build();
    }

    private static void buildParamField(RequestConfigBuilder builder,Class requestEntity,RequestParam[] requestParams){
        // 请求类型为非Map优先使用
        if(!Map.class.equals(requestEntity)){
            Field[] fields = PropertyUtil.getFields(requestEntity);
            for (Field field : fields) {
                RequestParam requestParam = field.getAnnotation(RequestParam.class);
                if(requestParam != null){
                    builder.addParamFields(getParamField(field,requestParam));
                }
                else {
                    builder.addParamFields(new ParamField(field.getName(),getFieldType(field)));
                }
            }
        }
        else if(requestParams != null && requestParams.length > 0){
            for (RequestParam requestParam : requestParams) {
                builder.addParamFields(getParamField(null,requestParam));
            }
        }
    }

    private static void buildParseField(RequestConfigBuilder builder,Class responseEntity,ResponseField[] responseFields){
        // 指定对象,优先使用对象解析
        if(!Map.class.equals(responseEntity) && responseEntity.isAnnotationPresent(ResponseEntity.class)){
            Field[] fields = PropertyUtil.getFields(responseEntity);
            for (Field field : fields) {
                ResponseField responseField = field.getAnnotation(ResponseField.class);
                if(responseField != null){
                    buildParseField(builder,null,field);
                }
            }
        }
        // 未指定对象,使用配置解析
        else if(responseFields != null && responseFields.length > 0){
            for (ResponseField responseField : responseFields) {
                String fieldName = responseField.fieldName();
                if(StringUtils.isBlank(fieldName)){
                    log.warn("ResponseField fieldName is blank.");
                    continue;
                }
                ParseField parseField = new ParseField(fieldName,responseField.fieldType(),
                        responseField.parseExpression(),responseField.expressionType());
                ChildrenField[] childrenFields = responseField.childrenField();
                if(responseField.fieldType() == ParseFieldType.OBJECT
                        && childrenFields!= null && childrenFields.length > 0){
                    for (ChildrenField childrenField : childrenFields) {
                        ParseField childField = new ParseField(childrenField.fieldName(),childrenField.fieldType(),
                                childrenField.parseExpression(),childrenField.expressionType());
                        parseField.addChildField(childField);
                    }
                }
                builder.addParseHtmlFields(parseField);
            }
        }
    }

    private static void buildParseField(RequestConfigBuilder builder, ParseField parentField,Field field){
        ResponseField responseField = field.getAnnotation(ResponseField.class);
        if(responseField != null){
            String fieldName = responseField.fieldName();
            if(StringUtils.isBlank(fieldName)){
                fieldName = field.getName();
            }
            ParseField parseField = new ParseField(fieldName,responseField.fieldType(),
                    responseField.parseExpression(),responseField.expressionType());
            if(responseField.fieldType() == ParseFieldType.OBJECT || responseField.fieldType() == ParseFieldType.LIST){
                Class<?> fieldType = field.getType();
                if(responseField.fieldType() == ParseFieldType.LIST){
                    Type genericType = field.getGenericType();
                    if(genericType != null) {
                        // 如果是泛型参数的类型
                        if (genericType instanceof ParameterizedType) {
                            ParameterizedType pt = (ParameterizedType) genericType;
                            //得到泛型里的class类型对象
                            fieldType = (Class<?>) pt.getActualTypeArguments()[0];
                        }
                    }
                }
                // 递归获取下级类型
                buildObjectParseField(null,parseField,fieldType);
            }
            if(parentField != null){
                parentField.addChildField(parseField);
            }
            else if(builder != null){
                builder.addParseHtmlFields(parseField);
            }
        }
    }

    private static void buildObjectParseField(RequestConfigBuilder builder, ParseField parentField, Class type){
        if(!Map.class.equals(type) && type.isAnnotationPresent(ResponseEntity.class)){
            Field[] fields = PropertyUtil.getFields(type);
            for (Field field : fields) {
                buildParseField(builder,parentField,field);
            }
        }
    }

    private static ParamFieldType getFieldType(Field field){
        Class<?> type = field.getType();
        if(Collection.class.equals(type)){
            return ParamFieldType.LIST;
        }
        else if(File.class.equals(type)){
            return ParamFieldType.FILE;
        }
        else if(int.class.equals(type) || long.class.equals(type)
                || double.class.equals(type) || float.class.equals(type)
                || Integer.class.equals(type) || Long.class.equals(type)
                || Double.class.equals(type) || Float.class.equals(type)){
            return ParamFieldType.NUMBER;
        }
        else {
            return ParamFieldType.STRING;
        }
    }

    private static ParamField getParamField(Field field, RequestParam ann) {
        String fieldName = null;
        if(StringUtils.isNotBlank(ann.fieldName())){
            fieldName = ann.fieldName();
        }
        else if(field != null){
            fieldName = field.getName();
        }
        if(StringUtils.isBlank(fieldName)){
            throw new RuntimeException("field name is blank.");
        }
        ParamField paramField = new ParamField(fieldName);
        if(StringUtils.isNotBlank(ann.defaultValue())){
            paramField.setDefaultValue(ann.defaultValue());
        }
        // fieldType为默认但类型不为String,优先采用类型对应的type
        ParamFieldType fieldType;
        if(field != null
                && ann.fieldType() == ParamFieldType.STRING
                && !String.class.equals(field.getType())){
            fieldType = getFieldType(field);
        }
        else {
            fieldType = ann.fieldType();
        }
        paramField.setFieldType(fieldType);
        paramField.setRequired(ann.required());
        if(StringUtils.isNotBlank(ann.defaultValue())){
            if(fieldType == ParamFieldType.LIST){
                paramField.setDefaultValue(Lists.charactersOf(ann.defaultValue()));
            }
            else {
                paramField.setDefaultValue(ann.defaultValue());
            }
        }
        if(StringUtils.isNotBlank(ann.example())){
            paramField.setExample(ann.example());
        }
        if(StringUtils.isNotBlank(ann.description())){
            paramField.setDescription(ann.description());
        }
        if(StringUtils.isNotBlank(ann.validation())){
            paramField.setValidation(ann.validation());
        }

        return paramField;
    }

}
